# coding=utf-8
# 采用coap lib实现的coap协议server
# 和client的demo
# 示例代码的连接：https://devpress.csdn.net/python/62fb54fac6770329307ffa9c.html

# 示例1：server.py

import aiocoap.resource as resource
import aiocoap

# 创建一个名为 server.py 的文件并添加一个名为 AlarmResource 的类。此类将管理警报资源的 CoAP 请求。
class AlarmResource(resource.Resource):
    """This resource supports the PUT method.
    PUT: Update state of alarm."""

    def __init__(self):
        super().__init__()
        self.state = "OFF"

    async def render_put(self, request):
        self.state = request.payload
        print('Update alarm state: %s' % self.state)

        return aiocoap.Message(code=aiocoap.CHANGED, payload=self.state)

import asyncio
def main():
    # Resource tree creation
    root = resource.Site()
    root.add_resource(['alarm'], AlarmResource())

    asyncio.Task(aiocoap.Context.create_server_context(root, bind=('localhost', 5683)))

    asyncio.get_event_loop().run_forever()

if __name__ == "__main__":
    main()

# 示例1：client_put.py
# 为了测试服务器，我们可以创建一个简单的客户端，它在每次运行时随机更新警报的状态（通过发送带有“ON”或“OFF”有效负载的 PUT 请求）。
import asyncio
import random

from aiocoap import *

async def main():
    context = await Context.create_client_context()
    alarm_state = random.choice([True, False])
    payload = b"OFF"

    if alarm_state:
        payload = b"ON"

    request = Message(code=PUT, payload=payload, uri="coap://localhost/alarm")

    response = await context.request(request).response
    print('Result: %s\n%r'%(response.code, response.payload))

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())


# 示例2：server.py
'''
实施 CoAP 观察选项
Observe 选项是 CoAP GET 方法的扩展。更准确地说，它是 GET 请求标头中的可选字段。当客户端使用 Observe 选项查询服务器时，它基本上会询问警报的当前状态，并且如果将来发生变化也会得到通知。
请注意,服务器不必遵守观察请求。例如，如果 CoAP 资源不支持观察者或已达到最大注册观察者。在这种情况下，Observe 选项将被忽略，请求将默认为普通的 GET 请求。
服务器还可以定期将资源的当前状态发送给所有注册的观察者。如果它没有收到任何观察者的回复,那么该观察者将从资源的注册观察者列表中删除。这是服务器用来在任何客户端静默消失时清理观察者列表的一种机制。
让我们通过更新类来继续实现。AlarmResource
现在，AlarmResource 将继承自 Aiocoap 的，而不是从 Aiocoap 的类继承。这将提供管理观察者的功能,我们只需要处理发送什么以及何时发送。ObservableResourceResource
让我们也更新对 PUT 请求的处理,以便当警报的状态更新时,它会设置一个标志来指示服务器通知观察者。
添加一个方法,该方法不断循环并检查是否设置了标志。如果设置了,那么服务器将通过调用向每个观察者发送更新。notify_observers_check()notify_observersrender_get()
'''
import aiocoap.resource as resource
import aiocoap
import asyncio
class AlarmResource(resource.ObservableResource):
    """This resource supports the PUT method.
    PUT: Update state of alarm."""

    def __init__(self):
        super().__init__()
        self.state = "OFF"
        self.has_observers = False
        self.notify_observers = False

    # Ensure observers are notify if required
    def notify_observers_check(self):
        while True:
            if self.has_observers and self.notify_observers:
                print('Notifying observers')
                self.updated_state()
                self.notify_observers = False

    # Observers change event callback
    def update_observation_count(self, count):
        if count:
            self.has_observers = True
        else:
            self.has_observers = False

    # Handles GET request or observer notify
    async def render_get(self, request):
        print('Return alarm state: %s' % self.status)
        payload = b'%s' % self.status.encode('ascii')
        return aiocoap.Message(payload=payload)
        
    # Handles PUT request
    async def render_put(self, request):
        self.status = request.payload.decode('ascii')
        print('Update alarm state: %s' % self.status)
        self.notify_observers = True

        return aiocoap.Message(code=aiocoap.CHANGED, payload=b'%s' % self.status.encode('ascii'))
        
logging.basicConfig(level=logging.INFO)
logging.getLogger("coap-server").setLevel(logging.DEBUG)
def main():
    # Resource tree creation
    root = resource.Site()
    alarmResource = AlarmResource()
    root.add_resource(['alarm'], alarmResource)
    asyncio.Task(aiocoap.Context.create_server_context(root, bind=('localhost', 5683)))

    # Spawn a daemon to notify observers when alarm status changes
    observers_notifier = threading.Thread(target=alarmResource.notify_observers_check)
    observers_notifier.daemon = True
    observers_notifier.start()

    asyncio.get_event_loop().run_forever()

if __name__ == "__main__":
    main()
        
# 示例2:client_observe.py
'''
为了测试 Observe 选项，我们可以创建另一个开始观察警报状态的客户端。
这个观察客户端也将使用 Aiocoap python 库。每当收到通知时，observe_callback（） 就会被调用。
'''
import logging
import asyncio
from aiocoap import *
logging.basicConfig(level=logging.INFO)
def observe_callback(response):
    if response.code.is_successful():
        print("Alarm status: %s" % (response.payload.decode('ascii')))
    else:
        print('Error code %s' % response.code)

async def main():
    context = await Context.create_client_context()

    request = Message(code=GET)
    request.set_request_uri('coap://localhost/alarm')
    request.opt.observe = 0
    observation_is_over = asyncio.Future()

    try:
        context_request = context.request(request)
        context_request.observation.register_callback(observe_callback)
        response = await context_request.response
        exit_reason = await observation_is_over
        print('Observation is over: %r' % exit_reason)
    finally:
        if not context_request.response.done():
            context_request.response.cancel()
        if not context_request.observation.cancelled:
            context_request.observation.cancel()

if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(main())
